﻿using System;
using System.Text;
using System.Xml;
using System.Collections.Generic;
using GeoRef.HttpUtils;
using GeoRef.Exceptions;
using System.Web;

namespace GeoRef.GeoQueries
{
    /// <summary>
    /// Encapsulates a geocoding query.
    /// </summary>
    class GeoCodingQuery
    {
        #region fields
        // ------------------------------------------------------------------------
        /// <value>
        /// Api key for Yahoo geocoding service.
        /// </value>
        private string m_Appid;

        /// <value>
        /// State of asked location
        /// </value>
        private string m_State;

        /// <value>
        /// City of asked location
        /// </value>
        private string m_City;

        /// <value>
        /// Zip code of asked location, only for USA and Canadian locations
        /// </value>
        private string m_Zip;

        /// <value>
        /// Street of asked location
        /// </value>
        private string m_Street;

        /// <value>
        /// A complete address of asked location
        /// </value>
        private string m_Location;

        /// <value>
        /// Url to which ask for geocoding operation
        /// </value>
        private string m_GeoCodingWebServiceUrl;
        // ------------------------------------------------------------------------
        #endregion

        #region constructors
        // ------------- constructors ---------------------------------------------
        /// <summary>
        /// Initializes a new GeoCodingQuery object.
        /// </summary>
        public GeoCodingQuery()
        {
            m_GeoCodingWebServiceUrl = null;
            m_Appid = null;
            m_State = null;
            m_City = null;
            m_Zip = null;
            m_Street = null;
            m_Location = null;
        }
        // ------------------------------------------------------------------------
        #endregion 

        #region properties
        // ------------ properties ------------------------------------------------
        /// <summary>
        /// The AppID for the geocoding operation.
        /// </summary>
        public string AppID
        {
            get { return m_Appid; }
            set { m_Appid = value; }
        }

        /// <summary>
        /// The geocoding webservice url.
        /// </summary>
        public string GeoCodingWebServiceUrl
        {
            get { return m_GeoCodingWebServiceUrl; }
            set { m_GeoCodingWebServiceUrl = value; }
        }

        /// <summary>
        /// The state parameter for the geocoding query.
        /// </summary>
        public string State
        {
            get { return m_State; }
            set { m_State = value; }
        }

        /// <summary>
        /// The city parameter for the geocoding query.
        /// </summary>
        public string City
        {
            get { return m_City; }
            set { m_City = value; }
        }

        /// <summary>
        /// The street parameter for the geocoding query.
        /// </summary>
        public string Street
        {
            get { return m_Street; }
            set { m_Street = value; }
        }

        /// <summary>
        /// The zip code parameter for the geocoding query.
        /// </summary>
        public string ZipCode
        {
            get { return m_Zip; }
            set { m_Zip = value; }
        }

        /// <summary>
        /// Gets or sets a complete address.
        /// </summary>
        public string Location
        {
            get { return m_Location; }
            set { m_Location = value; }
        }

        // --------------------------------------------------------------------------
        #endregion

        #region private methods
        
        /// <summary>
        /// Appends a new query string parameter to the given url.
        /// </summary>
        /// <param name="currentUrl">Url to append to</param>
        /// <param name="pName">Parameter Name</param>
        /// <param name="pValue">Parameter value</param>
        /// <param name="currentDescription">Exception message version of the query string</param>
        private void appendParamAndUpdateDescription(StringBuilder currentUrl, ref string pName, ref string pValue,
                StringBuilder currentDescription)
        {
            if (pValue != null)
            {
                string toAppend = pName + "=" + HttpUtility.UrlEncode(pValue);
                currentUrl.Append("&" + toAppend);
                currentDescription.Append(toAppend + "\n");
            }
        }

        /// <summary>
        /// Adds to addrElements the value of i-th tag called xmlTag
        /// from the given xml file, if it is not a empty string.
        /// </summary>
        /// <param name="xmlDoc">Xml file</param>
        /// <param name="xmlTag">Tag from which retrieve elements</param>
        /// <param name="index">Index of tag</param>
        /// <param name="addrElements">List of value to fill</param>
        private void addXmlElement(XmlDocument xmlDoc, string xmlTag, int index, List<string> addrElements)
        {
            XmlNodeList xmlNodeList = xmlDoc.GetElementsByTagName(xmlTag);
            string tmp;
            if (xmlNodeList.Count > 0 && index < xmlNodeList.Count)
            {
                tmp = xmlNodeList.Item(index).InnerText;
                if (!tmp.Trim().Equals(""))
                    addrElements.Add(tmp);
            }
        }
        // --------------------------------------------------------------------------
        #endregion

        #region public methods

        /// <summary>
        /// Performs the geocoding operation and, if possible, returns a result as a List&lt;GeoLoc&gt;.
        /// </summary>
        /// <exception cref="System.NullReferenceException">If any required parameter is missing 
        /// (e.g. appid, webservice url, etc...)</exception>
        /// <exception cref="GeoRef.Exceptions.GeoCodingException">If something goes
        /// wrong during the geocoding process.</exception>
        /// <returns>The geocoding result as a List&lt;GeoLoc&gt;.</returns>
        public List<GeoLoc> getGeocoding()
        {
            List<GeoLoc> allResult = new List<GeoLoc>();
            if (m_GeoCodingWebServiceUrl == null || m_Appid == null)
            {
                throw new NullReferenceException
                  ("Unable to do the geocoding request: some informations miss (webservice url or appid string)");
            }

            string baseUrl = m_GeoCodingWebServiceUrl + "?appid=" + m_Appid;
            StringBuilder yahooUrl = new StringBuilder(baseUrl);
            StringBuilder queryString = new StringBuilder();
            StringBuilder geocodingDescription = new StringBuilder();

            string[] pName = { "state", "city", "zip", "street", "location" };
            appendParamAndUpdateDescription(queryString, ref pName[4], ref m_Location, geocodingDescription);
            appendParamAndUpdateDescription(queryString, ref pName[3], ref m_Street, geocodingDescription);
            appendParamAndUpdateDescription(queryString, ref pName[2], ref m_Zip, geocodingDescription);
            appendParamAndUpdateDescription(queryString, ref pName[1], ref  m_City, geocodingDescription);
            appendParamAndUpdateDescription(queryString, ref pName[0], ref m_State, geocodingDescription);
            
            //the first replace is needed because the yahoo geocoding service seem to suffer when there is
            //a single quote in the query string (e.g. Sant'Andrea), giving strange results, 
            //but it likes double quotes (Sant"Andrea) O_o
            queryString.Replace("'", "\"").Replace(" ", "+");
            string finalUrl = yahooUrl.ToString() + queryString.ToString();

            XmlDocument xmlDoc;
            try
            {
                xmlDoc = HttpRequester.getXmlFromUri(finalUrl);
            }
            catch(Exception e)
            {
                string geoCodingError = "Error while processing the following geocoding request:\n" +
                geocodingDescription.ToString();
                geoCodingError += "\nError details: \n " + e.ToString() +"\n -----------------------";
                throw new GeoCodingException(geoCodingError);
            }

            XmlNode root = xmlDoc.DocumentElement;
            if (root.Name.Equals("ResultSet"))
            {
                XmlNodeList childs = root.ChildNodes;                
                int j = 0;
                while (j < childs.Count && childs[j].Name.Equals("Result")) {
                    XmlNodeList latitudeNode = xmlDoc.GetElementsByTagName("Latitude");
                    XmlNodeList longitudeNode = xmlDoc.GetElementsByTagName("Longitude");
                    string coords = latitudeNode.Item(j).InnerText + "," + longitudeNode.Item(j).InnerText;

                    List<string> addrElements = new List<string>();
                    string[] tagNames = new string[3] { "Address", "City", "State" };
                    int i = 0;

                    for (i = 0; i < 3; i++) {
                        addXmlElement(xmlDoc, tagNames[i], j, addrElements);
                    }

                    StringBuilder addrField = new StringBuilder();
                    for (i = 0; i < addrElements.Count; i++) {
                        if (i > 0) {
                            addrField.Append(", ");
                        } else {
                            addrElements[i] = addrElements[i].Replace(", &", " &");
                            addrElements[i] = System.Web.HttpUtility.HtmlDecode(addrElements[i]);
                            if ((addrElements[i].EndsWith("&") == true) ||
                                (addrElements[i].Contains("&#39") == true)) {
                                addrElements[i] = addrElements[i].Substring(0, addrElements[i].LastIndexOf(','));
                            }
                            addrElements[i] = addrElements[i].Replace(", ", " ");
                        }
                        addrField.Append(addrElements[i]);
                    }
                   
                    string address = System.Web.HttpUtility.UrlDecode(addrField.ToString());
                    

                    XmlAttribute warning = (XmlAttribute)root.FirstChild.Attributes.GetNamedItem("warning");
                    string descr = (warning != null) ? warning.InnerText : "";

                    GeoLoc gLoc = new GeoLoc(coords, address, descr);
                    allResult.Add(gLoc);
                    j++;
                }
                return allResult;
            }
            else
            {
                XmlNodeList errorNodes = xmlDoc.GetElementsByTagName("Message");
                string geoCodingError = errorNodes.Item(0).InnerText + "\n" + geocodingDescription.ToString();
                throw new GeoCodingException(geoCodingError);
            }
        }
        // ---------------------------------------------------------------------
        #endregion
    }

    // end class
}
